/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019.
 * Description: watchdog procfs
 * Author: xiaowei
 * Create: 2019-07-18
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/hal/watchdog.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

struct wtd_cmd {
	enum watchdog_cmd cmd;
	unsigned int value;
};

static DEFINE_MUTEX(proc_sem);
static char notify_taskname[TASK_COMM_LEN];
static int notify_sig = SIGSEGV;

static void send_signal(void)
{
	struct task_struct *p = NULL;
	int ret;

	if (!strlen(notify_taskname))
		return;

	read_lock(&tasklist_lock);
	for_each_process(p) {
		if (!strncmp(p->comm, notify_taskname, TASK_COMM_LEN)) {
			ret = __kill_pgrp_info(notify_sig, (struct kernel_siginfo *) 1, task_pgrp(p));
			pr_err(DTS_WDT_WARN "Sent signal %d to process %s[%d], ret value = %d\n",
					notify_sig, p->comm, p->pid, ret);
		}
	}
	read_unlock(&tasklist_lock);
}

int rtos_wtd_write(enum watchdog_cmd cmd, unsigned int value)
{
	int ret = 0;

	mutex_lock(&proc_sem);
	switch (cmd) {
	case CMD_STOP_ALL:
		rtos_wdt_stop();
		pr_info("CMD_STOP_ALL operation successful.\n");
		break;
	case CMD_EXTEND_TIME_HWDOG:
		hwdog_extend_feed_max(value);
		pr_info("CMD_EXTEND_TIME operation successful.\n");
		break;
	case CMD_RESET_TIME_HWDOG:
		hwdog_set_feed_count(0);
		hwdog_reset_feed_max();
		pr_info("CMD_RESET_TIME operation successful.\n");
		break;
	case CMD_SET_TIME_HWDOG:
		hwdog_set_feed_max(value);
		pr_info("CMD_SET_TIME operation successful.\n");
		break;
	case CMD_SET_INTERVAL_HWDOG:
		hwdog_set_interval(value);
		pr_info("CMD_SET_INTERVAL operation successful.\n");
		break;
	case CMD_FEED_HWDOG:
		call_hwdog_handle();
		break;
	case CMD_STOP_SOFTDOG:
		softdog_stop();
		pr_info("CMD_STOP_SOFTDOG operation successful.\n");
		break;
	case CMD_SET_INERTVAL_SOFTDOG:
		softdog_set_interval(value);
		break;
	case CMD_FEED_SOFTDOG:
		hwdog_set_feed_count(0);
		break;
	case CMD_SET_SEC_TIME_HWDOG:
		hwdog_set_sec_feed_max(value);
		pr_info("CMD_SET_SEC_TIME operation successful.\n");
		break;
	default:
		ret = -EINVAL;
		pr_info("Unknown operation.\n");
		break;
	}
	mutex_unlock(&proc_sem);

	return ret;
}
EXPORT_SYMBOL_NS(rtos_wtd_write, HW_RTOS_NS);

static ssize_t proc_wtd_write(struct file *filp, const char __user *buff,
			size_t len, loff_t *loff)
{
	struct wtd_cmd cmd;
	int ret;

	if (sizeof(struct wtd_cmd) != len) {
		pr_err(DTS_WDT_ERR "Invalid length!\n");
		return -EINVAL;
	}

	if (copy_from_user(&cmd, (void __user *)buff, len))
		return -EFAULT;

	ret = rtos_wtd_write(cmd.cmd, cmd.value);
	if (ret)
		return -EINVAL;

	return len;
}

static const struct proc_ops wtd_fops = {
	.proc_write = proc_wtd_write,
};

static void proc_wtd_create(void)
{
	if (!proc_create("wdt", S_IWUSR, NULL, &wtd_fops))
		pr_err(DTS_WDT_ERR"proc_wtd_create failed\n");
}

static ssize_t proc_taskname_write(struct file *filp, const char __user *buff,
		size_t len, loff_t *loff)
{
	ssize_t ret = len;

	if (len < 1 || len > TASK_COMM_LEN) {
		pr_err(DTS_WDT_ERR "input process name length invalid!\n");
		return -EINVAL;
	}

	mutex_lock(&proc_sem);
	if (copy_from_user(&notify_taskname, (void __user *)buff, len)) {
		ret = -EFAULT;
		goto unlock;
	}
	notify_taskname[len - 1] = '\0';
unlock:
	mutex_unlock(&proc_sem);
	return ret;
}

static int proc_taskname_show(struct seq_file *seq, void *v)
{
	seq_printf(seq, "%s\n", notify_taskname);
	return 0;
}

static int proc_taskname_open(struct inode *inode, struct file *file)
{
	return single_open(file, proc_taskname_show, NULL);
}

static const struct proc_ops taskname_fops = {
	.proc_open = proc_taskname_open,
	.proc_read = seq_read,
	.proc_lseek = seq_lseek,
	.proc_release = single_release,
	.proc_write = proc_taskname_write,
};

static ssize_t proc_signal_write(struct file *filp, const char __user *buff,
		size_t len, loff_t *loff)
{
	ssize_t ret = len;
	char buf[3];
	unsigned long val;

	if (len > 3 || len < 1) {
		pr_err(DTS_WDT_ERR "incorrect input signal length!\n");
		return -EINVAL;
	}

	mutex_lock(&proc_sem);
	if (copy_from_user(buf, (void __user *)buff, len)) {
		ret = -EFAULT;
		goto unlock;
	}

	buf[len - 1] = '\0';
	ret = kstrtoul(buf, 10, &val);
	if (ret)
		goto unlock;
	if (val > _NSIG || val == 0 || val == SIGSTOP || val == SIGKILL) {
		pr_err(DTS_WDT_ERR "input signal invalid.\n");
		ret = -EINVAL;
		goto unlock;
	}
	notify_sig = val;
	ret = len;
unlock:
	mutex_unlock(&proc_sem);
	return ret;
}

static int proc_signal_show(struct seq_file *seq, void *v)
{
	seq_printf(seq, "%d\n", notify_sig);
	return 0;
}

static int proc_signal_open(struct inode *inode, struct file *file)
{
	return single_open(file, proc_signal_show, NULL);
}

static const struct proc_ops signal_fops = {
	.proc_open = proc_signal_open,
	.proc_read = seq_read,
	.proc_lseek = seq_lseek,
	.proc_release = single_release,
	.proc_write = proc_signal_write,
};

static int proc_wtd_taskinfo_create(void)
{
	/* wdt is error, wtd is correct, bug we can't fix the file name */
	if (!proc_create("wdt_expt", S_IRUSR | S_IWUSR, NULL, &taskname_fops)) {
		pr_err(DTS_WDT_ERR "create interface taskname failed.\n");
		return -ENOMEM;
	}

	if (!proc_create("wdt_signal", S_IRUSR | S_IWUSR, NULL, &signal_fops)) {
		remove_proc_entry("wdt_expt", NULL);
		pr_err(DTS_WDT_ERR "create interface signal failed.\n");
		return -ENOMEM;
	}
	return 0;
}

void wtd_proc_init(void)
{
	proc_wtd_create();

	if (!proc_wtd_taskinfo_create())
		register_softdog_notify(send_signal);
}
